package com.yao.config;

import com.yao.filter.CustomClientCredentialsTokenEndpointFilter;
import com.yao.handler.*;
import com.yao.mygranter.DingtalkAuthTokenGranter;
import com.yao.service.IUserCodeService;
import com.yao.service.IUserDetailsService;
import com.yao.service.impl.RedisAuthorizationCodeServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

/**
 * Copyright (C), 2022-2022, 姚兵
 * Author: 32210
 * Date: 2022/11/2 13:35
 * FileName: Oauth2Config
 * Description:
 */

@Configuration
@EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {


    //提供了默认端点 URL（访问接口），我们进行Oauth2认证时，需要用到。
    //
    ///oauth/authorize：授权端点。
    ///oauth/token：令牌端点。
    ///oauth/confirm_access：用户确认授权提交端点。
    ///oauth/error：授权服务错误信息端点。
    ///oauth/check_token：用于资源服务访问的令牌解析端点。
    ///oauth/token_key：提供公有密匙的端点，如果你使用JWT令牌的话。
    @Autowired
    PasswordEncoder passwordEncoder;




    @Autowired
    RedisConnectionFactory connectionFactory;

    @Bean
    public RedisTokenStore redisTokenStore(){
        return new RedisTokenStore(connectionFactory);
    }

    @Autowired
    RedisTokenStore redisTokenStore;

    @Autowired
    IUserDetailsService userDetailsService;

    @Autowired
    MyAccessDenyHandler myAccessDenyHandler;

    @Autowired
    MyAuthenticationEntryPoint myAuthenticationEntryPoint;


    public static final class CompositeObjectPostProcessor {


        @Autowired
        private List<ObjectPostProcessor> postProcessors = new ArrayList<>();

        @SuppressWarnings({ "rawtypes", "unchecked" })
        public Object postProcess(Object object) {
            for (ObjectPostProcessor opp : postProcessors) {
                Class<?> oppClass = opp.getClass();
                Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass,
                        ObjectPostProcessor.class);
                if (oppType == null || oppType.isAssignableFrom(object.getClass())) {
                    object = opp.postProcess(object);
                }
            }
            return object;
        }

        /**
         * Adds an {@link ObjectPostProcessor} to use
         * @param objectPostProcessor the {@link ObjectPostProcessor} to add
         * @return true if the {@link ObjectPostProcessor} was added, else false
         */
        private boolean addObjectPostProcessor(
                ObjectPostProcessor<?> objectPostProcessor) {
            boolean result = this.postProcessors.add(objectPostProcessor);
            postProcessors.sort(AnnotationAwareOrderComparator.INSTANCE);
            return result;
        }
    }

    @Bean
    public CompositeObjectPostProcessor compositeObjectPostProcessor(){
        return new CompositeObjectPostProcessor();
    }

    @Autowired
    CompositeObjectPostProcessor compositeObjectPostProcessor;


    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {



        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
//                .allowFormAuthenticationForClients()
                .accessDeniedHandler(myAccessDenyHandler)
                .authenticationEntryPoint(myAuthenticationEntryPoint);

//        ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter();
//        clientCredentialsTokenEndpointFilter
//                .setAuthenticationManager(authenticationManager);
//        clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(myAuthenticationEntryPoint);
//
//        clientCredentialsTokenEndpointFilter.afterPropertiesSet();
//
//        clientCredentialsTokenEndpointFilter= (ClientCredentialsTokenEndpointFilter) compositeObjectPostProcessor.postProcess(clientCredentialsTokenEndpointFilter);
//
//        security.addTokenEndpointAuthenticationFilter(clientCredentialsTokenEndpointFilter);

        CustomClientCredentialsTokenEndpointFilter tokenEndpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
        tokenEndpointFilter.setAuthenticationEntryPoint(myAuthenticationEntryPoint);

//        OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint ();
//        authenticationEntryPoint.setTypeName ("Form");
//        authenticationEntryPoint.setRealmName ("oauth2/client");
//        // 使用自定义的 exceptionTranslator
//        authenticationEntryPoint.setExceptionTranslator (exceptionTranslator);

        CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter (security);
        // 必须首先执行这个，再继续接下来的配置
        endpointFilter.setAuthenticationEntryPoint (myAuthenticationEntryPoint);
        endpointFilter.afterPropertiesSet();
//        endpointFilter.setFilterProcessesUrl ("/login");

        security.addTokenEndpointAuthenticationFilter (endpointFilter);
    }



    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                // 使用内存设置
                .inMemory()
                // client_id
                .withClient("client")
                // client_secret
                .secret(passwordEncoder.encode("secret"))
                // 授权类型: 授权码、刷新令牌
                .authorizedGrantTypes("authorization_code","refresh_token","password","client_credentials","implicit","dingtalk")
                // 授权范围
                .scopes("app","user_info","admin")
//                .resourceIds("demo")
                // 注册回调地址
                .redirectUris("http://www.baidu.com/code")

                .and()
                .withClient("demo")
                // client_secret
                .secret(passwordEncoder.encode("demo"))
                // 授权类型: 授权码、刷新令牌
                .authorizedGrantTypes("authorization_code","refresh_token","password","client_credentials","implicit","dingtalk")
                // 授权范围
                .scopes("user_info")

                .resourceIds("demo")
                // 注册回调地址
                .redirectUris("http://www.baidu.com/code");

    }

    @Autowired
    AuthenticationManager authenticationManager;


    //基于redis
//    @Autowired
//    RedisAuthorizationCodeServices redisAuthorizationCodeServices;


    @Autowired
    DataSource dataSource;


    //基于数据库
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    @Autowired
    IUserCodeService userCodeService;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置端点允许的请求方式
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        // 配置认证管理器
        endpoints.authenticationManager(authenticationManager);

        endpoints.authenticationManager(authenticationManager).tokenStore(redisTokenStore).userDetailsService(userDetailsService);

        endpoints.authorizationCodeServices(authorizationCodeServices());

        ClientDetailsService clientDetails = endpoints.getClientDetailsService();
        AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
        OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();
        final List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
                requestFactory));
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
        ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
        tokenGranters.add(implicit);
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));

        tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager,tokenServices,clientDetails,requestFactory));
        tokenGranters.add(new DingtalkAuthTokenGranter(authenticationManager,tokenServices,clientDetails,userCodeService,userDetailsService,requestFactory));

        TokenGranter tokenGranter = new TokenGranter() {
            private CompositeTokenGranter delegate;

            @Override
            public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
                if (delegate == null) {
                    delegate = new CompositeTokenGranter(tokenGranters);
                }
                return delegate.grant(grantType, tokenRequest);
            }
        };

        endpoints.tokenGranter(tokenGranter);

        endpoints.exceptionTranslator(myExtendOAuth2ResponseExceptionTranslator);;
    }



    @Autowired
    MyExtendOAuth2ResponseExceptionTranslator myExtendOAuth2ResponseExceptionTranslator;
}
